/****************************************************************
 * i3_trigger.c - This file contains functions to send
 *                and process the packets received by an i3 server
 *
 *  Below is the main hierarchy of function calls from this file:
 *
 *         --------- forward_packet_i3 -------
 *         |             |                   |
 *         |             V                   |
 *         V      send_trigger_reply         V
 *  forward_packet_ip    |             forward_packet_chord
 *         |             V         
 *         |   send_opt_cache_address      send_opt_trigger_not_present     
 *         |   send_opt_cache_dest_address send_opt_ratelimit_exceeded 
 *         |             |                   |
 *         |             V                   |
 *         -------->send_packet_ip<-----------
 *                       |
 *                       V
 *            send_packet_ipv4_wrapper
 ****************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>   /* basic system data types */
#include <sys/socket.h>  /* basic socket definitions */
#include <arpa/inet.h>
#include <sys/time.h>    /* timeval{} for select() */
#include <errno.h>    
#include <sys/utsname.h>
#include <time.h>                /* timespec{} for pselect() */
#include <netinet/in.h>  /* sockaddr_in{} and other Internet defns */
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>

#include "i3.h"
#include "i3_fun.h"
#include "i3_api.h"
#include "i3_server.h"

#if NEWS_INSTRUMENT
#include "i3_server_info.h"
#include "i3_news.h"
#include "i3_news_instrument.h"
#endif

#include "chord_api.h"
#include "../utils/utils.h"

#include "i3_server_pkt.h"
#include "i3_server_utils.h"

extern unsigned short srv_port;

#if ACCEPT_TCP
#include "i3_tcp_fns.h"
#endif

/*********************************************************************
 * create_i3_opt_destination - create an I3_OPT_DESTINATION data 
 *                             structure
 * input:
 *   addr - destination's IP address
 *   port - destintation's port #
 *********************************************************************/

i3_option *create_i3_opt_destination(struct in_addr addr, uint16_t port)
{
  i3_addr	*a;
  i3_option     *o;

  /* insert I3_OPT_DESTINATION in the packet header */
  a = alloc_i3_addr();
  init_i3_addr_ipv4(a, addr, port);

  o = alloc_i3_option();
  init_i3_option(o, I3_OPT_DESTINATION, (void *)a);
  return o;
}


/*********************************************************************
 * send_packet_ipv4_wrapper - send packet to an end host
 *                       
 * input:
 *   ctx - server context
 *   pkt - packet to be sent
 *   len - packet length
 *   dst_addr, dst_port - IP address and port # of the destination
 *   dst_fd - file descriptor of the connection
 *********************************************************************/

void send_packet_ipv4_wrapper(srv_context *ctx, char *pkt, int len, 
			      struct in_addr *dst_addr, 
			      uint16_t dst_port, int dst_fd)
{
#if (ACCEPT_TCP)
  int tcp_socket;

  lookup_tcp_state(ctx->tcp_state, dst_addr->s_addr, dst_port, &tcp_socket);
  if (tcp_socket != -1) {
    int send_ret_val = send_tcp(pkt, len, tcp_socket);
    //printf("Using TCP socket, send_tcp returned %d\n", send_ret_val);
  }
  else
#endif
    send_packet_ipv4(pkt, len, dst_addr, dst_port, dst_fd);
}	


/*******************************************************************
 * 
 * send_packet_ip - send packet to an end-host either directly or
 *                  via an intermediate i3 server.
 *
 * input:
 *   ctx - context
 *   hdr - packet header 
 *   payload, payload_len - packet payload and its length. 
 *   to  - contains the address of the end-host (and of the  
 *         i3 server when I3_ADDR_TYPE_IPv4_NAT is used) where
 *         the packet is to be sent.
 *   sfd - descriptor of the connection 
 *
 * return:
 *
 * Notes:
 *   1) In the case of control messages payload=NULL and payload_len=0
 *   2) If payload exists we assume that there are MAX_HEADER_SIZE bytes
 *      in the front of the payload that can be used to prepend the
 *      header. This way we can avoid an extra copy of the payload.
 *******************************************************************/

void send_packet_ip(srv_context *ctx, 
		    i3_header *hdr,
		    int hdr_room,
		    char *payload, uint16_t payload_len,
		    i3_addr *to, int sfd)
{
  struct sockaddr_in dst_addr;
  i3_option         *o;
  uint16_t           len; 
  char               header[MAX_HEADER_LEN];
  char              *p;

  if (payload_len)
    p = payload;
  else {
    p = &header[MAX_HEADER_LEN-1];
    hdr_room = MAX_HEADER_LEN;
  }

  switch (to->type) {
  case I3_ADDR_TYPE_IPv4:
    len = get_i3_header_len(hdr);
    assert(hdr_room > len);
    pack_i3_header(p - len, hdr, &len);
    //printf("send_packet_ip, case I3_ADDR_TYPE_IPv4n");
    send_packet_ipv4_wrapper(ctx, p - len, 
			     len + payload_len, 
			     &to->t.v4.addr, 
			     to->t.v4.port, sfd);
    break;
  case I3_ADDR_TYPE_IPv4_NAT:
    /* the destination is behind a NAT */
    if ((to->t.v4_nat.i3srv_addr.s_addr == ctx->local_ip_addr.s_addr) &&
	(to->t.v4_nat.i3srv_port == ntohs(ctx->local.sin_port))) {
      /* the packet is sent directly to the destination;
       * convert all I3_ADDR_TYPE_IPv4_NAT addresses in the 
       * packet header to I3_ADDR_TYPE_IPv4 addresses.
       */
      translate_hdr_options_out(ctx, hdr, &dst_addr);
      len = get_i3_header_len(hdr);
      assert(hdr_room > len);
      pack_i3_header(p - len, hdr, &len);
      send_packet_ipv4_wrapper(ctx, p - len, 
			       len + payload_len, 
			       &to->t.v4_nat.nat_addr, 
			       to->t.v4_nat.nat_port, sfd);

    //printf("send_packet_ip, case I3_ADDR_TYPE_IPv4NAT_direct");
      
    } else {
      /* The packet needs to be sent to destination via
       * an intermediate i3 server. This is the i3 server 
       * to which the host has sent the packets. This is to 
       * ensure the NAT will accept the packets.
       * Add I3_OPT_DESTINATION option before sending packet;
       * the address carried in this option is used by the 
       * receiving i3 server to rely the packet to the 
       * final destination.  
       */
      o = create_i3_opt_destination(to->t.v4_nat.nat_addr, 
				    to->t.v4_nat.nat_port);
      if (!hdr->option_list)
	hdr->option_list = alloc_i3_option_list();
      append_i3_option(hdr->option_list, o);
      len = get_i3_header_len(hdr);
      assert(hdr_room > len);
      pack_i3_header(p - len, hdr, &len);
      send_packet_ipv4_wrapper(ctx, p - len, 
			       len + payload_len, 
			       &to->t.v4_nat.i3srv_addr, 
			       to->t.v4_nat.i3srv_port, sfd);
        //printf("send_packet_ip, case I3_ADDR_TYPE_IPv4NAT_indirect");
    }
    break;
  case I3_ADDR_TYPE_IPv6:
    wprintf("Address type not supported in send_packet_ip function,\n");
    break;
  default:
    wprintf("Invalid address type (%d) in send_packet_ip\n", to->type);
    break;
  }
}


/*************************************************************************
 * send_opt_trigger_not_present - generate and send a control message 
 *                                containing the I3_OPT_TRIGGER_NOT_PRESENT 
 *                                option
 *
 * input:
 *   ctx - context
 *   id  - ID for which there is no trigger stored locally
 *   to  - i3 address containing the final destination where the 
 *         packet needs to be sent
 *   sfd - connection's file descriptor 
 *************************************************************************/

void send_opt_trigger_not_present(srv_context *ctx, ID *id, 
				  i3_addr *to, int sfd)
{
  i3_header *h;
  i3_option *o;
  i3_option_list *ol;
  ID *id1;

  ol = alloc_i3_option_list();
  o = alloc_i3_option();
  if ((id1 = (ID *)malloc(ID_LEN)) == NULL)
    panic("send_data_reply: memory allocation erro.\n");
  memcpy((char *)id1, (char *)id, ID_LEN);
  init_i3_option(o, I3_OPT_TRIGGER_NOT_PRESENT, (void *)id1);
  append_i3_option(ol, o);

  h = alloc_i3_header();
  init_i3_header(h, FALSE, NULL, ol);

  send_packet_ip(ctx, h, MAX_HEADER_LEN, NULL, 0, to, sfd);

  free_i3_header(h);
}


/*************************************************************************
 * send_opt_ratelimit_exceeded - generate and send a control message 
 *                               containing the I3_OPT_TRIGGER_RATELIMIT
 *                               option
 *
 * input:
 *   ctx - context
 *   id  - ID for which there is no trigger stored locally
 *   to  - i3 address containing the final destination where the 
 *         packet needs to be sent
 *   sfd - connection's file descriptor 
 *************************************************************************/

void send_opt_ratelimit_exceeded(srv_context *ctx, ID *id, 
				 i3_addr *to, int sfd)
{
  i3_header *h;
  i3_option *o;
  i3_option_list *ol;
  ID *id1;

  ol = alloc_i3_option_list();
  o = alloc_i3_option();
  if ((id1 = (ID *)malloc(ID_LEN)) == NULL)
    panic("send_data_reply: memory allocation erro.\n");
  memcpy((char *)id1, (char *)id, ID_LEN);
  init_i3_option(o, I3_OPT_TRIGGER_RATELIMIT, (void *)id1);
  append_i3_option(ol, o);

  h = alloc_i3_header();
  init_i3_header(h, FALSE, NULL, ol);

  send_packet_ip(ctx, h, MAX_HEADER_LEN, NULL, 0, to, sfd);

  free_i3_header(h);
}


/* send I3_OPT_CACHE_ADDR packet */
void send_opt_cache_address(srv_context *ctx, ID *id, int prefix_len, 
			    i3_addr *to, int sfd)
{
  i3_header *h;
  i3_option *o;
  i3_option_list *ol;
  i3_trigger *t;
  i3_addr    *a;
  static Key null_key;

  ol = alloc_i3_option_list();
  o = alloc_i3_option();
  t = alloc_i3_trigger();
  a = alloc_i3_addr();
  init_i3_addr_ipv4(a, ctx->local_ip_addr, srv_port);
  init_i3_trigger(t, id, prefix_len, a, &null_key, 0);
  update_nonce(t);
  init_i3_option(o, I3_OPT_CACHE_ADDR, t);
  append_i3_option(ol, o);

  /* finish create the header */
  h = alloc_i3_header();
  init_i3_header(h, FALSE, NULL, ol);
  
  /* send packet */
  printf("send back server address\n");
  send_packet_ip(ctx, h, MAX_HEADER_LEN, NULL, 0, to, sfd);

  free_i3_header(h);
}

/* send I3_OPT_CACHE_ADDR packet */
void send_opt_cache_dest_address(srv_context *ctx, i3_trigger *tr, 
				 int prefix_len, 
				 i3_addr *to, int sfd)
{
  i3_header *h;
  i3_option *o;
  i3_option_list *ol;
  i3_trigger *t;
  i3_addr    *a;
  static Key null_key;

  ol = alloc_i3_option_list();
  o = alloc_i3_option();

  /* create I3_OPT_CACHE_ADDR option */
  t = alloc_i3_trigger();
  a = alloc_i3_addr();
  init_i3_addr_ipv4(a, ctx->local_ip_addr, srv_port);
  init_i3_trigger(t, &tr->id, prefix_len, a, &null_key, 0);
  update_nonce(t);
  init_i3_option(o, I3_OPT_CACHE_ADDR, t);
  append_i3_option(ol, o);

  /* create I3_OPT_CACHE_DEST_ADDR option */
  t = duplicate_i3_trigger(tr);
  o = alloc_i3_option();
  init_i3_option(o, I3_OPT_CACHE_DEST_ADDR, t);
  append_i3_option(ol, o);

  /* finish create the header */
  h = alloc_i3_header();
  init_i3_header(h, FALSE, NULL, ol);
  
  /* send packet */
  printf("send back server address and destination address\n");
  send_packet_ip(ctx, h, MAX_HEADER_LEN, NULL, 0, to, sfd);

  free_i3_header(h);
}


#ifndef __RTTI
#define __RTTI
#endif

/*************************************************************************
 * send_trigger_reply - genereate and send control messages in response
 *                      to the options contained in the header of a receiving
 *                      message
 *
 * input:
 *   ctx    - context
 *   tarray - array containing triggers; a control packet is generated
 *            and sent for each of these triggers
 *   opt_type_array - opt_type_array[i] represents the option
 *                    associated to the control packet generated for 
 *                    tarray[i]                     
 *   tnum   - size of arrays tarray and  opt_type_array, respectively.       
 *   to     - i3 address containing the final destination where the 
 *            control packet needs to be sent
 *   sfd    - connection's file descriptor 
 *************************************************************************/

void send_trigger_reply(srv_context *ctx, 
			void * __RTTI *tarray, char *opt_type_array, 
			int tnum, i3_addr *to, int sfd)
{
  int        i;
  i3_header *h;
  i3_option *o;
  i3_option_list *ol;
  i3_trigger *t, *t1;
  i3_addr *a;
  static Key null_key;

  for (i = 0; i < tnum; i++) {
    switch(opt_type_array[i]) {
    case I3_OPT_TRIGGER_ACK:
    case I3_OPT_TRIGGER_CHALLENGE:
    case I3_OPT_CONSTRAINT_FAILED:
    case I3_OPT_ROUTE_BROKEN:
      t1 = (i3_trigger *)tarray[i];
      ol = alloc_i3_option_list();

      /* TODO : add stack if the packet has to be sent to an id */
      
      /* create option for I3_OPT_TRIGGER_CHALLENGE or I3_OPT_TRIGGER_ACK */
      o = alloc_i3_option();
      init_i3_option(o, opt_type_array[i], 
		     (void *)duplicate_i3_trigger(t1));
      append_i3_option(ol, o);

      /* add option for I3_OPT_CACHE_ADDR -- sent with every trigger
       * ack and trigger challenge. Use a trigger data structure to
       * send I3_OPT_CACHE_ADDR */
      o = alloc_i3_option();
      t = alloc_i3_trigger();
      a = alloc_i3_addr();
      init_i3_addr_ipv4(a, ctx->local_ip_addr, srv_port);
      init_i3_trigger(t, &(t1->id), 
		      MIN_PREFIX_LEN /* this doesn't matter here */, 
		      a, &t1->key, 
		      (t1->flags & (~ I3_TRIGGER_FLAG_RATE_LIMIT)));
      update_nonce( t );

      init_i3_option(o, I3_OPT_CACHE_ADDR, t);
      append_i3_option(ol, o);
      /* finish to create the header */
      h = alloc_i3_header();
      init_i3_header(h, FALSE, NULL, ol);

      send_packet_ip(ctx, h, MAX_HEADER_LEN, NULL, 0, to, sfd);

      /* send packet */
      if (opt_type_array[i] == I3_OPT_TRIGGER_ACK)
	printf("Send back ack\n");
      else if (opt_type_array[i] == I3_OPT_TRIGGER_CHALLENGE)
	printf("Send back challenge\n");
      else if (opt_type_array[i] == I3_OPT_CONSTRAINT_FAILED)
	printf("Send back constraint failed\n");
      else
	printf("Send back route broken\n");
	
      free_i3_header(h);
      break;

    case I3_OPT_CACHE_ADDR:
      send_opt_cache_address(ctx, (ID *)tarray[i], MIN_PREFIX_LEN, to, sfd);
      break;

    default:
      panic("send_trigger_reply: unexpected option_type\n");
    }
  }
  
}


/***************************************************************************
 *
 * forward_packet_ip - Forward a data packet that has matched a trigger 
 *                     pointing to i3 address "to".
 *
 * input:
 *   ctx    - context
 *   hdr, header_room - packet header and its length
 *   payload, payload_len - packet payload and its length
 *   to     - i3 address containing the final destination where the 
 *            control packet needs to be sent
 *   to_end_hosts - specifies whether this packet is forwarded to an end
 *                  host or to an ID
 *************************************************************************/

void forward_packet_ip(srv_context *ctx, 
		       i3_header *hdr, int header_room, 
		       char *payload, int payload_len,
		       i3_addr *to, 
		       int to_end_host)
{
    char	   *pkt_start;
    i3_header	   *new_hdr;

    /* remove options and form a new header */
    if (get_i3_option_from_list(hdr->option_list,
				I3_OPT_REQUEST_FOR_CACHE_SHORTCUT_INDIR)) {
     new_hdr = duplicate_i3_header(hdr);
    } else {
      new_hdr = alloc_i3_header();
      if (to_end_host)
	init_i3_header(new_hdr, 1, hdr->stack, 0);
      else
	init_i3_header(new_hdr, 1, hdr->stack, hdr->option_list);
    }

    send_packet_ip(ctx, new_hdr, header_room, 
		   payload, payload_len, to, ctx->fd);
    
    /* just free the header object, and not what is inside
     * those are freed when hdr is freed at the end of process_initial 
     */
    free(new_hdr);
}



/**************************************************************************
 * forward_packet_chord - Forward an i3 packet along chord if no cache 
 *                        entries exist to directly sent to the node 
 *                        responsible.
 * input:
 *   hdr - packet header; the first ID in the stack represents the 
 *         the ID of the packet weher the packet is to be forwarded via
 *         Chord
 *   payload, payload_len - the packet payload and its length
 *************************************************************************/

void forward_packet_chord(i3_header *hdr, char *payload, int payload_len)
{
  uint16_t	hdr_len, packet_len;
  char 		*packet_start;
  chordID	key;

  hdr_len = get_i3_header_len(hdr);
  packet_start = payload - hdr_len;
  packet_len = payload_len + hdr_len;

  pack_i3_header(packet_start, hdr, &hdr_len);
  memmove(key.x, get_first_id(packet_start), CHORD_ID_BITS/8 * sizeof(char));
  
  chord_route(&key, packet_start, packet_len);
}


/***************************************************************************
 * forward_packet_i3 - Forward a packet matching a trigger storred locally
 * 
 * input:
 *   ctx - context
 *   hdr - packet header
 *   header_room - maximum number of available bytes that preceedes
 *                 the packet payload and can be used to store the header   
 *   payload, payload_len - packet payload and its length
 *   t_node - matching trigger 
 *
 * Note: hdr->option_list might already contain some options now
 **************************************************************************/

void forward_packet_i3(srv_context *ctx, 
		       i3_header *hdr, int header_room, 
		       char *payload, int payload_len,
		       trigger_node *t_node)
{
  ID			*id = hdr->stack->ids;
  srv_id_entry	*cache_entry;
  i3_option		*opt_sender;
  i3_addr		*to;
  struct in_addr	ia;
  
  /* check whether the id that the packet is
   * forwarded to has a valid cache entry */
  cache_entry = srv_get_valid_id_entry(ctx->id_cache, id, &ctx->now);
  
  /* add I3_OPT_SENDER anyway */
  opt_sender = create_i3_option_sender(ctx);
  append_i3_option(hdr->option_list, opt_sender);

  /* check if the node has died, and if so fwd by chord and send a
   * reply back to the person who inserted the trigger (oops!) */
  if (NULL != cache_entry) {
    i3_addr *temp = &(cache_entry->addr);
    if ((temp->type != I3_ADDR_TYPE_IPv4)) {
      printf("%x\n", temp->type);
      printf("process_packet_2: invalid address type.\n");
    } 
#if 0
    else if (monitor_is_dead(&(ctx->mon_list), 
			     temp->t.v4.addr.s_addr, temp->t.v4.port)) {
      char opt_type[1];
      void *t[1];
      // Commented out as not sure if this is needed
      opt_type[0] = I3_OPT_ROUTE_BROKEN;
      t[0] = t_node->trigger;
      /* Note: Trigger removed as well */
      send_trigger_reply(ctx, t, opt_type, 1, t_node->ret_a, ctx->fd);
      remove_trigger(ctx->trigger_hash_table, t_node->trigger);
      cache_entry = NULL;
    } 
#endif
    else {
      // printf("Link fine, forwarding\n");
    }
  }
  
  if (NULL == cache_entry) {
    i3_option *o_req_cache;
    ia.s_addr = htonl(ctx->local_ip_addr.s_addr);
    
    /* no entry in the cache for this "id" -- request to cache it */
    fprintf(stderr, "FI3: At %s:%d, no cache entry for id\n",
	    inet_ntoa(ia), ctx->local.sin_port);
    fprintf_i3_id(stderr, id, 0);
    
    o_req_cache = create_i3_option_req_cache();
    append_i3_option(hdr->option_list, o_req_cache);
    
    /* forward packet via underlying DHT */
    forward_packet_chord(hdr, payload, payload_len);
  } else {
    if (srv_time_to_refresh_id_entry(cache_entry, &ctx->now)) {
      /* time to refresh cache entry */
      i3_option *o_req_cache = create_i3_option_req_cache();
      append_i3_option(hdr->option_list, o_req_cache);
      cache_entry->retries_cnt++;
      cache_entry->last_ping = ctx->now;
    } else {
      /* just add I3_SENDER_OPTION to receive eventual error messages
       * such as I3_TRIGGER_NOT_PRESENT (for now, do nothing here) 
       */
    }

    /* send packet to i3 node addr via IP */
    to = &cache_entry->addr;
    if ((to->type != I3_ADDR_TYPE_IPv4) && (to->type != I3_ADDR_TYPE_IPv6)) {
      printf("%x\n", to->type);
      panic("process_packet_2: invalid address type.\n");
    }
    /* printf("Cache entry available: forwarding via IP\n"); */
    forward_packet_ip(ctx, hdr, header_room, payload, payload_len,
		      &cache_entry->addr, 0);
  }
}


/*****************************************************************
 * is_sender_natted - Check whether the sender of the message with the 
 *                    header 'hdr' is behind a NAT. This 
 *                    function is evaluated at the first i3 hop. 
 * 
 *  input:
 *    hdr       - i3 header of the packet
 *    from_addr - IP source address and port # of the packet's sender
 *
 *  return:
 *    TRUE if the sender of the packet is behind a NAT, FALSE 
 *    otherwise,
 *
 *  Note: We assume the sender is natted if:
 *  
 *  - 'hdr' contains an I3_OPT_SENDER options whose addres 
 *    (this is the sender's address) is different from the
 *    source address of the packet ('from_addr'). Note that if 
 *    the sender is not behind a NAT, the two addresses are the same.
 *
 *  - 'hdr' contains a trigger in its option list that points to
 *    the sender, and the sender address in the trigger data 
 *    structure is different from the packet's source address
 * 
 *****************************************************************/
 
int is_sender_natted(i3_header *hdr, struct sockaddr_in* from_addr)
{
  i3_option      *o;
  i3_trigger     *t;

  assert(hdr);
  assert(from_addr);

  if (hdr->option_list == NULL) {
    wprintf("is_sender_nated: no option list!\n");
    return FALSE;
  }
  for (o = hdr->option_list->head; o; o = o->next) {
    switch (o->type) {
    case I3_OPT_SENDER:
      if (o->entry.ret_addr->type == I3_ADDR_TYPE_IPv4) {
	if ((o->entry.ret_addr->t.v4.addr.s_addr != 
	     from_addr->sin_addr.s_addr) || 
	    (o->entry.ret_addr->t.v4.port != from_addr->sin_port))
	  return TRUE;
	else
	  return FALSE;
      }
      break;
    case I3_OPT_TRIGGER_INSERT:
    case I3_OPT_TRIGGER_ACK:
    case I3_OPT_TRIGGER_REMOVE:
      t = o->entry.trigger;
      if (t->to->type == I3_ADDR_TYPE_IPv4) {
	if ((t->to->t.v4.addr.s_addr != from_addr->sin_addr.s_addr) || 
	    (o->entry.ret_addr->t.v4.port != from_addr->sin_port))
	  return TRUE;
	else
	  return FALSE;
      }
      break;
    default:
      ;
    }
  }
  return FALSE;
}


/*****************************************************************
 * is_exit_i3srv - Check whether this is an exit i3 server for
 *                 the packet with header 'hdr'. 
 * 
 *  input:
 *    hdr       - i3 header of the packet
 *
 *  return:
 *    TRUE is this is an exit node, FALSE otherwise,
 *
 * Note: We assume that this is an exit node if the packet
 *       header contains an I3_OPT_DESTINATION option
 *****************************************************************/
 
int is_exit_i3srv(i3_header *hdr)
{
  i3_option  *o;
  i3_trigger *t;

  assert(hdr);

  if (hdr->option_list == NULL) {
    wprintf("is_exit_i3srv: no option list!\n");
    return FALSE;
  }
  for (o = hdr->option_list->head; o; o = o->next) {
    switch (o->type) {
    case I3_OPT_DESTINATION:
      return TRUE;
    }
  }
  return FALSE;
}


/*********************************************************************
 *  translate_ipv4_to_ipv4nat - Convert an I3_ADDR_TYPE_IPv4 record 
 *                              into an I3_ADDR_TYPE_IPv4_NAT record.
 *                              The I3_ADDR_TYPE_IPv4 record was created
 *                              by an end host, possible behind a NAT.
 *  input:
 *    ctx - server context
 *    addr - i3 address to be converted; the fields of addr
 *           are coppied in the host_* fileds of the 
 *           I3_ADDR_TYPE_IPv4_NAT record
 *    from_addr - IP address and the port number of the 
 *                packet's sender are copied in the nat_* fields
 *                of the I3_ADDR_TYPE_IPv4_NAT record field
 *
 * return:
 *   an address of type I3_ADDR_TYPE_IPv4_NAT  
 *********************************************************************/

i3_addr *translate_ipv4_to_ipv4nat(srv_context *ctx, 
				   i3_addr *addr,
				   struct sockaddr_in* from_addr)
{
  i3_addr *new_addr;

  assert(addr);
  assert(addr->type == I3_ADDR_TYPE_IPv4);

  /* the corresponding packet is in transit;
   * replace addr with a two-hop address (the 1st address is
   * is this i3 node address, and the 2nd address is the sender address
   */
  new_addr = alloc_i3_addr();
  init_i3_addr_ipv4_nat(new_addr,
			addr->t.v4.addr,
			addr->t.v4.port,
			from_addr->sin_addr, 
			from_addr->sin_port,
			ctx->local_ip_addr,
			ntohs(ctx->local.sin_port));
  free_i3_addr(addr);
  return new_addr; 
}


/*********************************************************************
 *  translate_ipv4nat_to_ipv4 - Convert an I3_ADDR_TYPE_IPv4_NAT record 
 *                              into an I3_ADDR_TYPE_IPv4 record.
 *  input:
 *    ctx - server context
 *    addr - i3 address to be converted; the nat_* fields of addr
 *           are coppied into the I3_ADDR_TYPE_IPv4 record
 *
 * return:
 *   an address of type I3_ADDR_TYPE_IPv4  
 *********************************************************************/

i3_addr *translate_ipv4nat_to_ipv4(srv_context *ctx, 
				   i3_addr *addr)
{
  i3_addr *new_addr;

  assert(addr);
  assert(addr->type == I3_ADDR_TYPE_IPv4_NAT);

  new_addr = alloc_i3_addr();
  init_i3_addr_ipv4(new_addr, 
		    addr->t.v4_nat.host_addr,
		    addr->t.v4_nat.host_port);
  free_i3_addr(addr);
  return new_addr; 
}


/**************************************************************************
 *  translate_hdr_options_in - Translate the options in the packet 
 *     header. This function is invoked when the packet sent by a host 
 *     H reaches the first i3 server S, and we want to enforce
 *     that all packets that are sent back to H are sent via S. 
 *     This allows us the support hosts behind NATs and firewalls. 
 *     
 *     This function translates all i3 addresses of type I3_ADDR_TYPE_IPv4 
 *     that contains the IP sender address into addresses of type
 *     I3_ADDR_TYPE_IPv4_NAT. In addition to the  sender address, 
 *     an I3_ADDR_TYPE_IPV4 NAT type address contains (1) the IP source address
 *     and the port # from the packet header (this address is different from 
 *     the IP address recorded by the sender in the I3_ADDR_TYPE_IPv4 address, 
 *     when the host is behind a NAT), and the IP address / port # of the
 *     local i3 server. 
 *
 * input:
 *   ctx - contex
 *   hdr - packet header to be translated
 *   flag_tcp_snd - specifies whether the packet has been received 
 *                  on a TCP connection 
 *   from_addr - IP address and port # of the packet's sender
 *
 *************************************************************************/

void translate_hdr_options_in(srv_context *ctx, 
			      i3_header   *hdr, 
			      uint8_t      flag_tcp,
			      struct sockaddr_in* from_addr)
{
  i3_option  *o;
  i3_trigger *t;

  assert(hdr);
  assert(from_addr);

  if (hdr->option_list == NULL) {
    wprintf("translate_hdr_options_in: no option list!\n");
    return;
  }
    
  for (o = hdr->option_list->head; o; o = o->next) {
    switch (o->type) {
    case I3_OPT_SENDER:
      if (o->entry.ret_addr->type == I3_ADDR_TYPE_IPv4) 
	o->entry.ret_addr = translate_ipv4_to_ipv4nat(ctx, 
						      o->entry.ret_addr, 
						      from_addr); 
      break;
    case I3_OPT_TRIGGER_INSERT:
    case I3_OPT_TRIGGER_ACK:
    case I3_OPT_TRIGGER_REMOVE:
      t = o->entry.trigger;
      if (t->to->type == I3_ADDR_TYPE_IPv4) 
	t->to = translate_ipv4_to_ipv4nat(ctx,
					  t->to, from_addr); 
      if (flag_tcp)
	/* if the receiver uses tcp disable shortcuts (here
         * we implicitly assume that the sender uses tcp because
         * it is behind a firewall that dosen't allow udp connections
         * even if they are initiated by the receiver).
	 * NOTE: if receiver doesn't use TCP, shortcuts
	 * remain enabled even if the receiver is behind a NAT
         */
	t->flags &= (~I3_TRIGGER_FLAG_ALLOW_SHORTCUT);
      break;
    case I3_OPT_REQUEST_FOR_CACHE_SHORTCUT:
      /* disable the shortcut request of a sender connected 
       * through a TCP connection
       */
      if (flag_tcp)
	o->type = I3_OPT_REQUEST_FOR_CACHE;
      else
	o->type = I3_OPT_REQUEST_FOR_CACHE_SHORTCUT_INDIR;
      break;
    case I3_OPT_TRIGGER_CHALLENGE:
    case I3_OPT_TRIGGER_NOT_PRESENT:
    case I3_OPT_CONSTRAINT_FAILED:
    case I3_OPT_ROUTE_BROKEN:
      /* these options are sent only by the i3 infrastructure;
       * they cannot be sent by a client 
       */
      wprintf("Invalid option type %d in translate_hdr_options_in.\n",
	      o->type);
      break;
    case I3_OPT_CACHE_SHORTCUT_ADDR: 
      /* this option is send directly from an end host to another end host */
      wprintf("Invalid option type %d in translate_hdr_options_in.\n",
	      o->type);
      break;
    case I3_OPT_DESTINATION:
    case I3_OPT_REQUEST_FOR_CACHE:
    case I3_OPT_CACHE_ADDR:
    case I3_OPT_FORCE_CACHE_ADDR:
       /* do nothing */ 
      break;
      
    default:
      wprintf("Unknown option type %d in translate_hdr_options_in.\n", 
	      o->type);
    }
  }
}

/**************************************************************************
 *  translate_hdr_options_out - Translate the options in the packet 
 *     header at the last i3 server, before the packet is sent to an
 *     end-host. 
 *
 *     This function translates all i3 addresses of type 
 *     I3_ADDR_TYPE_IPv4_NAT into address into addresses of type
 *     I3_ADDR_TYPE_IPv4. In doing so, the destination IP address 
 *     and the port # in the I3_ADDR_TYPE_IPv4 record are set to the
 *     the IP address and port # of the nat field from the 
 *     I3_ADDR_TYPE_IPv4_NAT address. This way, the packet is delivered
 *     to the natted end hosts.
 *
 * input:
 *   ctx - contex
 *   hdr - packet header to be translated
 *   dst_addr - IP address and port # where the packet needs to be forwarded
 *
 *************************************************************************/

void translate_hdr_options_out(srv_context *ctx, 
			       i3_header *hdr, 
			       struct sockaddr_in* dst_addr)
{
  i3_option      *o, *onext;
  i3_trigger     *t;
  i3_addr        *a;

  assert(hdr);
  assert(dst_addr);

  if (hdr->option_list == NULL) {
    wprintf("translate_hdr_options_out: no option list!\n");
    return;
  }
  for (o = hdr->option_list->head; o;) {
    switch (o->type) {
    case I3_OPT_DESTINATION:
      dst_addr->sin_addr.s_addr = o->entry.ret_addr->t.v4.addr.s_addr;
      dst_addr->sin_port = o->entry.ret_addr->t.v4.port;
      /* remove option */
      onext = o->next;
      remove_i3_option_from_list(hdr->option_list, o);
      o = onext;
      break;

    case I3_OPT_TRIGGER_CHALLENGE:
    case I3_OPT_CONSTRAINT_FAILED:
    case I3_OPT_ROUTE_BROKEN:
    case I3_OPT_TRIGGER_ACK:
      t = o->entry.trigger;
      if (t->to->type == I3_ADDR_TYPE_IPv4_NAT) 
	t->to = translate_ipv4nat_to_ipv4(ctx, t->to);
      o = o->next;
      break;

    case I3_OPT_TRIGGER_INSERT:
    case I3_OPT_TRIGGER_REMOVE:
      /* these options are sent only to the i3 infrastructure;
       * they cannot be sent by the i3 infrastructure to end hosts 
       */
      wprintf("Invalid option type %d in translate_hdr_options_out.\n",
	      o->type);
      o = o->next;
      break;
    case I3_OPT_CACHE_SHORTCUT_ADDR: 
      /* this option is send directly from an end host to another end host */
      wprintf("Invalid option type %d in translate_hdr_options_out.\n",
	      o->type);
      o = o->next;
      break;
    case I3_OPT_CACHE_ADDR:
      /* if the client uses TCP, it might not be able to 
       * use the cache information, so cache on behalf of
       * the client. This way, when the client sends a packet
       * with the same ID next time, we can forward the 
       * packet directly to the node responsible for the ID.
       */
      if (is_id_local(&o->entry.trigger->id, ctx)) 
	/* no need to cache; the ID is stored at this i3 server! */
	;
      else 
	srv_insert_id_entry(ctx->id_cache,
			    &o->entry.trigger->id,
			    o->entry.trigger->to, &ctx->now);
      o = o->next;
      break;
    case I3_OPT_REQUEST_FOR_CACHE:
    case I3_OPT_REQUEST_FOR_CACHE_SHORTCUT_INDIR:
    case I3_OPT_FORCE_CACHE_ADDR:
      o = o->next;
      /* do nothing */ 
      break;
    default:
      wprintf("Unknown option type %d in translate_hdr_options_in.\n", 
	      o->type);
      o = o->next;
    }
  }
}



